package com.soup.lab.executor;

import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;

import java.util.concurrent.Callable;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;


/**
 * 线程指的是程序中的执行线程，JVM允许应用中存在多个线程同步执行。
 * <p>
 * 每个线程都有优先级，优先级越高的线程越优先执行
 * 线程的优先级默认为创建它的线程得优先级
 * 守护线程创建的线程都是守护线程
 * <p>
 * 在JVM启动的时候，通常有一个非守护的线程。
 * JVM在下面一个条件发生时，会停止线程的执行：
 * 1. 调用 {@link Runtime#exit(int)}
 * 2. 所有非守护线程都死亡了，或者它们的run方法都执行完成或抛出了异常
 * <p>
 * 有两种创建执行线程的方法：
 * 1. 继承{@link Thread}类，并重写它的 {@link Thread#run}方法
 * 2. 实现{@link Runnable}接口，并将其作为参数传递给Thread构造器
 * 线程池允许使用{@link Callable}接口实现作为有返回值的任务提交，
 * 线程池拿到Callable后会将其转换为{@link java.util.concurrent.FutureTask}，
 * 而FutureTask实现了Runnable。
 * <p>
 * 每个线程都有一个名称来作为标识，这个名称在查看日志时会比较有用。
 * <p>
 * 线程类中有许多native方法，并且线程是直接与操作系统打交道的，
 * 此处只学习该类中每个方法的执行表现，不追究其实现。
 */
public class ThreadTestCase {

    /**
     * 获取当前执行的线程
     * <p>
     * 当前线程属于main组，父组为system组，system中有四个守护线程
     */
    @Test
    public void testCurrentThread() {
        Thread thread = Thread.currentThread();
        System.out.println(thread);
    }

    /**
     * 让出执行当前线程处理器的使用，是否有效取决于线程调度器
     * 有很少的场景会使用到该方法，可能在调试或测试中会使用到，
     * 在并发控制结构中会很有用，例如 {@link java.util.concurrent.locks} package下。
     * <p>
     * 使用countDownLatch来排除掉线程创建的消耗
     * 额外创建处理器数量的线程，尽量保证没有空闲的处理器，增加测试成功的几率
     */
    @Test
    public void testYield() {
        // 返回的是JVM可用的处理器的数量，并不一定是机器的核心数，4核返回的8
        int availableProcessors = Runtime.getRuntime().availableProcessors();
        CountDownLatch createLatch = new CountDownLatch(availableProcessors);
        CountDownLatch startLatch = new CountDownLatch(1);
        for (int i = 0; i < availableProcessors; i++) {
            new Thread(() -> {
                createLatch.countDown();
                try {
                    startLatch.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                int j = 0;
                while (j++ < 10) {
                    System.out.println(Thread.currentThread().getName() + ":" + j);
                }
            }, "线程" + i).start();
        }
        try {
            createLatch.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        startLatch.countDown();
        Thread.yield();
        System.out.println("b");
    }

    /**
     * sleep(..)
     * 使当前线程休眠指定时间，休眠期间线程不会失去任何monitor的所有权
     */
    @Test
    public void testSleep() {
        final Object mutex = new Object();
        CountDownLatch latch = new CountDownLatch(1);
        CountDownLatch endMainLatch = new CountDownLatch(2);
        new Thread(() -> {
            synchronized (mutex) {
                latch.countDown();
                try {
                    System.out.println("go to bed");
                    Thread.sleep(1000);
                    System.out.println("sleep thread awake");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            endMainLatch.countDown();
        }, "sleep thread").start();

        new Thread(() -> {
            try {
                latch.await();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("try lock");
            synchronized (mutex) {
                System.out.println("get lock");
            }
            endMainLatch.countDown();
        }, "try-lock thread").start();
        try {
            endMainLatch.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

    }

    /**
     * 被创建线程会“继承”该线程本地变量
     */
    private static InheritableThreadLocal<String> INHERITABLE_DEAR_NAME = new InheritableThreadLocal<String>() {
    };
    /**
     * 被创建线程不会“继承”该线程本地变量
     */
    private static ThreadLocal<String> DEAR_NAME = new ThreadLocal<String>() {
    };

    /**
     * Thread的构造方法都会调用 init 方法来初始化线程
     * <code>
     * private void init(ThreadGroup g, Runnable target, String name,
     * long stackSize, AccessControlContext acc, boolean inheritThreadLocals)
     * </code>
     * 1. 以创建线程的当前线程作为被创建线程的父线程
     * 2. 如果没有指定线程组，那么将会加入到父线程的线程组
     * 3. g.addUnstarted(); 线程组未启动的线程数+1
     * 4. this.daemon = parent.isDaemon() “继承”父线程的守护线程属性
     * 5. this.priority = parent.getPriority() “继承”父线程的优先级
     * 6. 设置上下文类加载器，如果security为空或ContextClassLoader相关方法被重写了，就调用getContextClassLoader，否则就直接赋值为父类的上下文类加载器
     * 7. if (inheritThreadLocals && parent.inheritableThreadLocals != null)
     * this.inheritableThreadLocals = ThreadLocal.createInheritedMap(parent.inheritableThreadLocals); “继承”父线程的本地变量
     * 8. 设置线程 ID
     */
    @Test
    public void testNew() {
        ThreadGroup group = new ThreadGroup("test");
        INHERITABLE_DEAR_NAME.set("ray");
        DEAR_NAME.set("jack");
        Thread thread = new Thread(group, () -> {
            // 被创建线程会“继承”该本地变量
            Assertions.assertEquals(INHERITABLE_DEAR_NAME.get(), "ray");
            INHERITABLE_DEAR_NAME.set("abc");
            // 被创建线程不会“继承”该本地变量
            Assertions.assertNull(DEAR_NAME.get());
        }, "dear-printer");
        thread.start();
        System.out.println(INHERITABLE_DEAR_NAME.get());
        Assertions.assertEquals(thread.getThreadGroup(), group);
    }

    private static InheritableThreadLocal<StringWrapper> INHERITABLE_DEAR_NAME2 = new InheritableThreadLocal<StringWrapper>() {
    };

    private static class StringWrapper {
        private String str;

        public StringWrapper(String str) {
            this.str = str;
        }

        public String getStr() {
            return str;
        }

        public void setStr(String str) {
            this.str = str;
        }

        @Override
        public String toString() {
            return "StringWrapper{" +
                    "str='" + str + '\'' +
                    '}';
        }
    }

    @Test
    public void testNew2() throws InterruptedException {
        ThreadGroup group = new ThreadGroup("test");
        INHERITABLE_DEAR_NAME2.set(new StringWrapper("ray"));
        Thread thread = new Thread(group, () -> {
            // 被创建线程会“继承”该本地变量
            INHERITABLE_DEAR_NAME2.get().setStr("abc");
        }, "dear-printer");
        thread.start();
        TimeUnit.SECONDS.sleep(1);
        System.out.println(INHERITABLE_DEAR_NAME2.get());
    }

    /**
     * start方法不会被 main 方法线程 或 由虚拟机创建和设置的 "system" 线程组中的线程调用
     * 1. 判断 threadStatus 是否为 0-初始状态
     * 2. 将当前线程加入到线程组中，线程组线程数+1，线程组未启动的线程数-1
     * 3. 尝试调用本地方法启动线程，如果启动失败，则移除该线程，线程组未启动的线程数+1
     */
    @Test
    public void testStart() {
        ThreadGroup group = new ThreadGroup("test");
        System.out.println(group.activeCount());
        Thread thread = new Thread(group, () -> {
            System.out.println(group.activeCount());
        }, "dear-printer");
        Thread thread2 = new Thread(group, () -> {
            System.out.println(group.activeCount());
        }, "dear-printer2");
        thread.start();
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        thread2.start();
    }

    /**
     * 如果线程是使用一个额外的 Runnable 运行对象，那么会调用这个 Runnable 对象的 run 方法
     * 此外，这个方法不会做任何操作，然后返回
     * <p>
     * Thread 的子类应该重写该方法
     */
    public void testRun() {
    }

    /**
     * 由系统调用该方法，在实际退出前给线程一个清除的机会
     * <p>
     * 1. 调用线程组的线程终止方法，从线程组移除该线程。
     * 在满足一定条件时销毁线程组（1.这是一个守护线程组；2.线程组中没有存活的或未执行的线程；3.没有子线程组 ）。
     * 2. 将所有的引用字段都设置为null
     */
    public void testExit() {
    }

    /**
     * 打断线程
     * <p>
     * 除非当前线程在中断它自己，这总会被允许，否则会调用该线程的checkAccess方法，这可能会产生一个SecurityException
     * <p>
     * 1 如果在调用下列方法的过程中阻塞：
     * {@link Object#wait()} {@link Thread#join(long)} {@link Thread#sleep(long)} 和其他重载方法
     * 那么它的中断状态会被清除并且会收到一个 {@link InterruptedException}.
     * 在执行interrupt后，interrupt标志会置为true；在抛出InterruptedException异常后，interrupt标志会被清除
     *
     * <p>
     * 2 如果线程在 {@link java.nio.channels.InterruptibleChannel InterruptibleChannel} 的IO操作中阻塞
     * 那么线程的中断状态会被设置并且线程会收到一个 {@link java.nio.channels.ClosedByInterruptException}
     * <p>
     * 3 如果这个线程在 {@link java.nio.channels.Selector} 中阻塞，那么线程的中断状态会被设置并且它会立即从
     * 选择操作中返回，通常是一个非零值，就像调用了 {@link  java.nio.channels.Selector#wakeup wakeup} 一样。
     * <p>
     * 4 如果不在上述情况下，那么线程的中断状态会被设置
     * <p>
     * 5 不在激活状态的线程不会有任何影响
     *
     * Thread.interrupted 返回打断标志，并清除标志
     */
    @Test
    public void testInterruptWait() {
        CountDownLatch latch = new CountDownLatch(1);
        Thread waitThread = new Thread(() -> {
            synchronized (waitLock) {
                latch.countDown();
                try {
                    while (true)
                        waitLock.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                    Assertions.assertFalse(Thread.currentThread().isInterrupted());
                }
            }
        });
        waitThread.start();
        try {
            latch.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        Assertions.assertFalse(waitThread.isInterrupted()); // false
        waitThread.interrupt();
        Assertions.assertTrue(waitThread.isInterrupted()); // true
    }

    private Object waitLock = new Object();

    /**
     * join 方法内部使用了 {@link Object#wait()} 实现，由于锁定的就是当前的线程对象，
     * 所以直接采用了方法加锁的方式确保获取当前线程的monitor锁。
     */
    @Test
    public void testInterruptJoin() {
        Thread mainThread = Thread.currentThread();
        Thread joinThread = new Thread(() -> {
            System.out.println("I LOVE YOU");
            mainThread.interrupt();
        });
        joinThread.start();
        try {
            // join方法阻塞的是当前线程，join内部是用 Object#wait方法实现的
            joinThread.join();
            System.out.println("I LOVE YOU TOO");
        } catch (InterruptedException e) {
            e.printStackTrace();
            Assertions.assertFalse(Thread.currentThread().isInterrupted());
        }
    }

    @Test
    public void testInterruptInterruptibleChannel() {
        // TODO
    }

    @Test
    public void testInterruptSelector() {
        // TODO
    }

    @Test
    public void testIsAlive() throws InterruptedException {
        Thread thread = new Thread();
        thread.start();
        thread.join();
        Assertions.assertFalse(thread.isAlive());
    }

    /**
     * 测试当前线程是否拥有指定对象的monitor锁
     */
    @Test
    public void testHoldsLock() {
        Object lock = new Object();
        Assertions.assertFalse(Thread.holdsLock(lock));
        synchronized (lock) {
            Assertions.assertTrue(Thread.holdsLock(lock));
        }
        Assertions.assertFalse(Thread.holdsLock(lock));
    }

    @Test
    public void testGetState() throws InterruptedException {
        Thread mainThread = Thread.currentThread();
        Object lock = new Object();
        CountDownLatch sleepLatch = new CountDownLatch(1);
        Thread thread = new Thread(() -> {
            Assertions.assertEquals(Thread.currentThread().getState(), Thread.State.RUNNABLE);
            try {
                sleepLatch.countDown();
                synchronized (lock) {
                    Thread.sleep(1000);
                }
                Assertions.assertEquals(mainThread.getState(), Thread.State.WAITING);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });
        Assertions.assertEquals(thread.getState(), Thread.State.NEW);
        thread.start();
        Assertions.assertEquals(Thread.currentThread().getState(), Thread.State.RUNNABLE);
        sleepLatch.await();
        // thread睡眠中
        Assertions.assertEquals(thread.getState(), Thread.State.TIMED_WAITING);
        Thread blockThread = new Thread(() -> {
            synchronized (lock) {
                System.out.println("get lock");
            }
        });
        blockThread.start();
        Thread.sleep(100);
        // thread睡眠持有lock, blockThread尝试获取lock monitor锁失败，进入阻塞状态
        Assertions.assertEquals(blockThread.getState(), Thread.State.BLOCKED);
        thread.join();
        // thread执行结束
        Assertions.assertEquals(thread.getState(), Thread.State.TERMINATED);
    }

}
